
package models.aaa;

import java.util.AbstractList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.NoResultException;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Transient;
import javax.persistence.TypedQuery;

import models.re.IItem;

import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.Query;
import org.hibernate.search.SearchException;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.IndexedEmbedded;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.query.dsl.QueryBuilder;

import play.db.jpa.JPA;

/**
 * 
 * @author laurent@opprecht.info
 */
@Entity
@Indexed
// @Table(uniqueConstraints = { @UniqueConstraint(columnNames = { "about_id" }),
// @UniqueConstraint(columnNames = { "url_id" }) })
public class Asset extends Node implements IItem {

    /**
     * Control access to the asset
     * 
     * @author laurent
     * 
     */
    public class Access {

        /**
         * Return true if an actor is allowed to access the resource. False
         * otherwise.
         * 
         * @param actor
         * @return
         */
        public boolean isAllowed(final Asset actor) {
            if (getVisibility() == Visibility.PUBLIC) {
                return true;
            }

            if (actor != null) {
                if (getLinks().contains(actor)) {
                    return true;
                }
            }

            if (getVisibility() == Visibility.INHERIT && getGroup() != null) {
                return getGroup().access().isAllowed(actor);
            }

            return false;

        }
    }

    /**
     * Search persistent storage
     * 
     * @author laurent
     */
    public static class Finder {

        public TypedQuery<Asset> createQuery(final String sql) {
            TypedQuery<Asset> result = JPA.em().createQuery(sql, Asset.class);
            return result;
        }

        public TypedQuery<Asset> query(final String sql, final Object... parameters) {
            TypedQuery<Asset> result = createQuery(sql);
            for (int i = 0; i < parameters.length; i++) {
                result.setParameter(i + 1, parameters[i]);
            }
            return result;
        }

        public FullTextQuery with(final String text) {
            return with(text, null);
        }

		public FullTextQuery with(final String text, final Asset actor) {
            if (StringUtils.isEmpty(text)) {
                return null;
            }

            try {
                EntityManager em = JPA.em();
                FullTextEntityManager fullTextEntityManager = org.hibernate.search.jpa.Search
                        .getFullTextEntityManager(em);
                //QueryBuilder qb = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(File.class).get();
                // Query tika = qb.keyword().onField("filename").ignoreFieldBridge().matching(text).createQuery();

                QueryBuilder qb = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(Asset.class)
                        .get();

                Query beValid = qb.keyword().onFields("isValid").matching(true).createQuery();
                Query match = qb.keyword().onFields("name", "description","file.filename").ignoreFieldBridge().matching(text).createQuery();

                Query beAccessible = null;
                if (actor != null) {
                    Query bePublic = qb.keyword().onFields("visibility").matching(Visibility.PUBLIC).createQuery();
                    Query beMine = qb.keyword().onFields("links").matching(actor).createQuery();

                    Query bePublicGroup = qb.keyword().onFields("group.visibility").matching(Visibility.PUBLIC)
                            .createQuery();
                    Query belongToGroup = qb.keyword().onFields("group.links").matching(actor).createQuery();

                    beAccessible = qb.bool().should(bePublic).should(beMine).should(bePublicGroup)
                            .should(belongToGroup).createQuery();
                } else {
                    Query bePublic = qb.keyword().onFields("visibility").matching(Visibility.PUBLIC).createQuery();
                    Query bePublicGroup = qb.keyword().onFields("group.visibility").matching(Visibility.PUBLIC)
                            .createQuery();

                    beAccessible = qb.bool().should(bePublic).should(bePublicGroup).createQuery();
                }

                Query query = qb.bool().must(beValid).must(beAccessible).must(match).createQuery();
                //Query query = qb.bool().must(beValid).must(match).createQuery();
                //Query query = qb.bool().must(match).createQuery();

                // String q = query.toString();

                FullTextQuery result = fullTextEntityManager.createFullTextQuery(query, Asset.class);
//                System.out.println(result);
//				@SuppressWarnings("unchecked")
//				List<File> f = result.getResultList();
//                for(File file: f) System.out.println(file.getAsset().getDescription());
                return result;
            } catch (SearchException ex) {
                /**
                 * Note that searching on stopwords: a, the, etc will throw an
                 * exception. This reason being stopwords are removed (they are
                 * not significant).
                 */
                return null;
            }
        }
        
        public FullTextQuery withType(final String text, final Asset actor, final String type) {
            if (StringUtils.isEmpty(text)) {
                return null;
            }

            try {
                EntityManager em = JPA.em();
                FullTextEntityManager fullTextEntityManager = org.hibernate.search.jpa.Search
                        .getFullTextEntityManager(em);

                QueryBuilder qb = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(Asset.class)
                        .get();

                Query beValid = qb.keyword().onFields("isValid").matching(true).createQuery();
                Query match = qb.keyword().onFields("name", "description").matching(text).createQuery();

                Query beAccessible = null;
                if (actor != null) {
                    Query bePublic = qb.keyword().onFields("visibility").matching(Visibility.PUBLIC).createQuery();
                    Query beMine = qb.keyword().onFields("links").matching(actor).createQuery();

                    Query bePublicGroup = qb.keyword().onFields("group.visibility").matching(Visibility.PUBLIC)
                            .createQuery();
                    Query belongToGroup = qb.keyword().onFields("group.links").matching(actor).createQuery();

                    beAccessible = qb.bool().should(bePublic).should(beMine).should(bePublicGroup)
                            .should(belongToGroup).createQuery();
                } else {
                    Query bePublic = qb.keyword().onFields("visibility").matching(Visibility.PUBLIC).createQuery();
                    Query bePublicGroup = qb.keyword().onFields("group.visibility").matching(Visibility.PUBLIC)
                            .createQuery();

                    beAccessible = qb.bool().should(bePublic).should(bePublicGroup).createQuery();
                }
                
                Query query;
                if (type == null) {
                        query = qb.bool().must(beValid).must(beAccessible).must(match).createQuery();//TODO ne pas oublier de changer !
//                      query = qb.bool().must(beValid).must(match).createQuery();
                } else {
                        Query beOfType = qb.keyword().onFields("type").matching(Type.valueOf(type)).createQuery();
                    query = qb.bool().must(beValid).must(beAccessible).must(match).must(beOfType).createQuery();//TODO ne pas oublier de changer !
//                    query = qb.bool().must(beValid).must(match).must(beOfType).createQuery();
                }

                FullTextQuery result = fullTextEntityManager.createFullTextQuery(query, Asset.class);
                
                return result;
            } catch (SearchException ex) {
                /**
                 * Note that searching on stopwords: a, the, etc will throw an
                 * exception. This reason being stopwords are removed (they are
                 * not significant).
                 */
                return null;
            }
        }

        public Asset me(final String domain, final String value) {
            Identifier id = Identifier.find.byValue(domain, value);
            if (id == null) {
                return null;
            }

            Asset result = id.getAsset();
            if (Type.ACTOR != result.getType()) {
                return null;
            }

            return result;
        }

        public TypedQuery<Asset> mySpaces(final Asset actor) {
            String sql = "SELECT e FROM Asset e WHERE e.type = ? AND e.link = ?";
            TypedQuery<Asset> result = query(sql, Type.SPACE, actor);
            return result;
        }

        public TypedQuery<Asset> query(final String sql) {
            return query(sql, new Object[] {});
        }

        public Asset byId(final Long id) {
            return JPA.em().find(Asset.class, id);
        }

        public Asset byResource(final Resource resource) {
            if (resource == null) {
                return null;
            }
            if (resource.getId() == null) {
                return null;
            }

            String sql = "SELECT e FROM Asset e WHERE e.resource = ?";
            List<Asset> items = query(sql, resource).getResultList();
            for (Asset item : items) {
                return item;
            }
            return null;
        }

        public Asset byUrl(final String url) {
            Resource resource = Resource.find.byUrl(url);
            if (resource == null) {
                return null;
            }
            return resource.getAsset();
        }

        public Asset byIdentifier(final String domain, final String value) {
            Identifier pid = Identifier.get(domain, value);
            if (pid == null) {
                return null;
            }
            return pid.getAsset();
        }
        
        public Asset byLid(final String lid) {
        	String sql = "SELECT e FROM Asset e WHERE lid = ?";        
            javax.persistence.Query query = JPA.em().createQuery(sql);
    		query.setParameter(1, lid);
    		try {
    			return (Asset)query.getSingleResult();
    		}
    		catch(NoResultException s) {
    			return null;
    		}
        }
        
        public Asset byUid(final String uid) {
        	String sql = "SELECT e FROM Asset e WHERE e.uid = ?";        
            javax.persistence.Query query = JPA.em().createQuery(sql);
    		query.setParameter(1, uid);
    		return (Asset)query.getSingleResult();
        }

    }

    public static final Finder find             = new Finder();
    /**
     * 
     */
    @SuppressWarnings("unused")
    private static final long  serialVersionUID = 8744068302677756989L;

    public enum Type {
        EMPTY, ACTOR, ASSET, ACTIVITY, SPACE, COMMENT;
    }

    public enum Visibility {
        INHERIT, // Inherit visibility from parent space
        PUBLIC, // All access is public: content and description
        PRIVATE, // Content is private, description is public
        HIDDEN; // Content is private, description is private
    }

    public static Asset get(final String url) {
        return get(Resource.get(url));
    }

    public static Asset get(final Resource resource) {
        if (resource == null) {
            return null;
        }
        //if(resource.getAsset()==null) resource.setAsset(new Asset());
        return resource.getAsset();
    }

    public static boolean has(final Asset des) {
        if (des == null) {
            return false;
        }
        if (des.getId() == null) {
            return false;
        }
        return find.byId(des.getId()) != null;
    }

    @Transient
    private final Access    access      = new Access();

    @Enumerated(EnumType.STRING)
    @Field()
    private Type            type        = Type.EMPTY;

    /**
     * Resources that are mapped to this asset and that describe it.
     */
    @OneToMany(cascade = { CascadeType.ALL }, mappedBy = "asset", fetch = FetchType.LAZY)
    private Set<Resource>   resources   = new HashSet<Resource>();

    private String          uid         = null;
    private String          lid         = null;
    private String          dataset     = null;

    @IndexedEmbedded(depth = 2)
    @OneToOne(cascade = { CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE })
    private Asset           group       = null;

    @Field()
    private String          name        = null;

    @Lob
    @Field()
    @Column(length = 2048)
    private String          description = null;
    @IndexedEmbedded(depth = 2)
    @OneToOne(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY)
    private File			file		= null;
    private String          thumbnail   = null;

    @Field()
    private boolean         isValid     = false;

    @Enumerated(EnumType.STRING)
    @Field()
    private Visibility      visibility  = Visibility.HIDDEN;

    @IndexedEmbedded()
    @ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.DETACH, CascadeType.MERGE })
    private final Set<Tag>  tags        = new HashSet<Tag>();

    @Field()
    @IndexedEmbedded(depth = 1)
    @ManyToMany(targetEntity = Asset.class, cascade = { CascadeType.PERSIST, CascadeType.DETACH, CascadeType.REFRESH,
            CascadeType.MERGE }, fetch = FetchType.LAZY)
    @JoinTable(name = "asset_link", joinColumns = @JoinColumn(name = "from_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "to_id", referencedColumnName = "id"))
    private Set<Asset>      links       = new HashSet<Asset>();

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "asset", orphanRemoval = true)
    private Set<Identifier> identifiers = new HashSet<Identifier>();

    public Asset() {

    }

    public Asset(final Resource resource) {
        resources.add(resource);
    }

    public Access access() {
        return access;
    }

    /**
     * @return the id
     */
    @Override
    public Long getId() {
        return id;
    }

    /**
     * @param id
     */
    protected void setId(final Long id) {
        this.id = id;
    }

    /**
     * @return the type
     */
    public Type getType() {
        return type;
    }

    /**
     * @param type
     */
    public void setType(final Type type) {
        this.type = type;
    }

    public Visibility getVisibility() {
        return visibility;
    }

    public void setVisibility(final Visibility visibility) {
        this.visibility = visibility;
    }

    public Asset getGroup() {
        return group;
    }

    public void setGroup(final Asset group) {
        this.group = group;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name
     */
    public void setName(final String name) {
        this.name = name;
    }

    /**
     * @return the description
     */
    public String getDescription() {
        return description;
    }
    
    public String getDescription(int lenght) {
		if(lenght<description.length()) return description.substring(0, lenght);
		return description;
    }

    /**
     * @param description
     */
    public void setDescription(final String description) {
        this.description = description;
    }

    /**
     * @return the tags
     */
    public Set<Tag> getTags() {
        return tags;
    }

    public void add(final Tag item) {
        if (!getTags().contains(item)) {
            getTags().add(item);
        }
    }

    public void add(final Set<Tag> items) {
        for (Tag item : items) {
            add(item);
        }
    }

    /**
     * @param tags
     */
    public void setTags(final List<Tag> tags) {
        this.tags.addAll(tags);
    }

    /**
     * @return the dataset
     */
    public String getDataset() {
        return dataset;
    }

    /**
     * @param dataset
     */
    public void setDataset(final String dataset) {
        this.dataset = dataset;
    }

    /**
     * @return the uid
     */
    public String getUid() {
        return uid;
    }

    /**
     * @param uid
     */
    public void setUid(final String uid) {
        this.uid = uid;
    }

    /**
     * @return the lId
     */
    public String getLid() {
        return lid;
    }

    /**
     * @param lId
     */
    public void setLid(final String lId) {
        this.lid = lId;
    }

    public boolean isValid() {
        return isValid;
    }

    public void setValid(final boolean value) {
        isValid = value;
    }

    public String getThumbnail() {
        return thumbnail;
    }

    public void setThumbnail(final String thumbnail) {
        this.thumbnail = thumbnail;
    }

    /**
     * @return the links
     */
    public Set<Asset> getLinks() {
        return links;
    }

    void setLinks(final Set<Asset> value) {
        links = value;
    }

    public Set<Resource> getResources() {
        return resources;
    }

    void setResources(final Set<Resource> value) {
        resources = value;
    }

    public void add(final Resource item) {
        if (!getResources().contains(item)) {
            getResources().add(item);
        }
        if (item.getAsset() != this) {
            item.setAsset(this);
        }
    }

    public Set<Identifier> getIdentifiers() {
        return identifiers;
    }

    void setIdentifiers(final Set<Identifier> value) {
        identifiers = value;
    }

    public void add(final Identifier item) {
        if (!getIdentifiers().contains(item)) {
            getIdentifiers().add(item);
        }
        if (item.getAsset() != this) {
            item.setAsset(this);
        }
    }
    
    private List<Character> asList(final char[] string) {
        return new AbstractList<Character>() {
           public int size() { return string.length; }
           public Character get(int index) { return string[index]; }
           public Character set(int index, Character newVal) {
              char old = string[index];
              string[index] = newVal;
              return old;
           }
           public String toString() {
        	   StringBuilder builder = new StringBuilder(string.length);
        	    for(Character ch: string) builder.append(ch);
        	    return builder.toString();
           }
        };
    }
    
    public void anonymise() {
    	if(this.name!=null) {
    		List<Character> tmp = asList(this.name.toCharArray());
    		Collections.shuffle(tmp);
    		this.name=tmp.toString();
    	}
    	if(this.description!=null) {
    		List<Character> tmp = asList(this.description.toCharArray());
    		Collections.shuffle(tmp);
    		this.description=tmp.toString();    	
    	}
    	if(this.uid!=null) {
    		List<Character> tmp = asList(this.uid.toCharArray());
    		Collections.shuffle(tmp);
    		this.uid=tmp.toString();    	
    	}
    	//this.save();
    }

	@Override
	public String getStrId() {
		// TODO Auto-generated method stub
		return Long.toString(super.getId());
	}

	public void setFile(final String file) {
		if(file!=null) this.file=File.get(file,false);
	}
	
	public String getFilename() {
		if(this.file==null) return null;
		return this.file.getURL();
	}

}
